/** 
  A D 2.0 unit test framework inspired by Ruby's Unit::Test

// Written in the D programming language 1.0 || 2.0

Authors:	Wei Li (oldrev@gmail.com)
License:	BSD
Copyright:	Copyright (C) 2007 by Wei Li.
*/

module dotmars.test.core;

import dotmars.base.signals;
import dotmars.base.tuple;

version(Windows)
	private const char PATH_SEP = '\\';
else
	private const char PATH_SEP = '/';
////////////////////////////////////////////////////////////////////////////////

struct Failure
{
	string location;
	string message;
	string testName;
}

////////////////////////////////////////////////////////////////////////////////

struct Error
{
	Exception exception;
	string testName;
	string location;
}

////////////////////////////////////////////////////////////////////////////////

class TestResult
{
	private Error[]	m_errors;
	private Failure[] m_fails;
	private int m_runCount;
	private int m_assertionCount;
	private int m_testCount;

	Error[] errors() {
		return m_errors;
	}

	Failure[] failures() {
		return m_fails;
	}

	void addFailure(string loc, string msg, string name)
	{
		Failure f;
		with(f) {
			location = loc;
			message = msg;
			testName = name;
		}
		m_fails ~= f;
	}

	void addError(Exception ex, string name, string loc) 
	{
		Error e;
		with(e) {
			exception = ex;
			testName = name;
			location = loc;
		}
		m_errors ~= e;
	}

	void addAssertion() {
		m_assertionCount++;
	}

	void addTest() {
		m_testCount++;
	}

	void addRun() {
		m_runCount++;
	}

	bool hasPassed() {
		return m_errors.length == 0 && m_fails.length == 0;
	}

	int errorCount() {
		return cast(int)m_errors.length;
	}

	int failureCount() {
		return cast(int)m_fails.length;
	}

	int runCount() {
		return m_runCount;
	}

	int testCount() {
		return m_testCount;
	}

	int assertionCount() {
		return m_assertionCount;
	}
}


////////////////////////////////////////////////////////////////////////////////


abstract class TestBase
{
	protected this() { 
	}
	
	abstract void run(TestResult result);

	abstract bool isRunning();

	abstract string className();

	string fileName() 
	{
		string ret = className.dup;
		foreach(inout char c; ret)
			if(c == '.') c = PATH_SEP;
		ret ~= ".d";
		return ret;
	}

	string name()
	{
		return removeModuleName(className);
	}

	protected static string removeModuleName(string name)
	{
		size_t i = 0;
		foreach_reverse(char c; name)
		{
			if(c == '.') break;
			i++;
		}

		return name[$ - i .. $].dup;
	}
}


////////////////////////////////////////////////////////////////////////////////


abstract class TestCase(Subclass) : TestBase
{
	/*
	version(D_Version2) 
	{
		private static string ctf_makeInitCode(T)()
		{
			string ret;
			foreach(str; __traits(derivedMembers, T)) {
				if(str[0..4] == "test")
					ret ~= `register("` ~ str ~ `", &sc.` ~ str ~ `); ` ~ "\n";
			}
			return ret;
		}
	}*/

	alias typeof(this)	SelfType;

	public const string m_className = Subclass.classinfo.name;
	public alias void delegate() Test;

	private Test[string]	m_methods;

	private TestResult		m_result;
	private string	m_currentMethod;
	private bool	m_isFailed;
	private bool	m_running = false;

	this(Test[string] methods) 
	{
		m_methods = methods;
	}

	this()
	{
	}

	/*
	version(D_Version2) 
	{
	private void initial(Subclass sc) {
			mixin(ctf_makeInitCode!(Subclass)());
	}
	}*/

	Test[string] tests() {
		return m_methods;
	}

	override string className() {
		return m_className;
	}

	void register(string name, Test method) {
		m_methods[name] = method;
	}

	void setup() 
	{
	}

	void teardown() 
	{
	}

	override bool isRunning() {
		return m_running;
	}

	override void run(TestResult result) 
	{
		m_result = result;
		m_result.addRun();

		foreach(testName; m_methods.keys)
		{
			m_isFailed = false;
			m_currentMethod = name;
			m_result.addTest();
			setup();
			m_running = true;

			try {
				m_methods[testName]();
			}
			catch(Exception ex) {
				m_result.addError(ex, currentMethodName, fileName());
			}
			finally {
				m_running = false;
			}

			teardown();
		}
	}

	string currentMethodName() {
		return name() ~ "." ~ m_currentMethod;
	}

	private void addFailure(string message = null)
	{
		if(!m_isFailed)
		{
			m_isFailed = true;
			m_result.addFailure(fileName(), message, currentMethodName);
		}
	}

//////////////////////////// Assertion Functions ///////////////////////////

	void assertTrue(bool x, lazy string message = null) 
	{
		m_result.addAssertion();
		if(!x) {
			addFailure(message);
		}
	}

	void assertNull(T)(T value, lazy string message = null)
	{
		m_result.addAssertion();
		if(value !is null) {
			addFailure(message);
		}
	}

	void assertNotNull(T)(T value, lazy string message = null)
	{
		m_result.addAssertion();
		if(value is null) {
			addFailure(message);
		}
	}

	void assertEqual(T)(T expected, T actual, lazy string message = null)
	{
		m_result.addAssertion();
		if(expected != actual) {
			addFailure(message);
		}
	}

	void assertNotEqual(T)(T expected, T actual, T delta, lazy string message = null)
	{
		m_result.addAssertion();
		if(expected == actual) {
			addFailure(message);
		}
	}

	void flunk(lazy string message = "Flunked")
	{
		m_result.addAssertion();
		addFailure(message);
	}

}


////////////////////////////////////////////////////////////////////////////////

class TestSuite(Subclass) : TestBase
{
	alias typeof(this)	SelfType;

	protected string m_className;
	private TestBase[]	m_tests;
	private bool m_running  = false;

	this()
	{
		static if(is(Subclass : TestBase))
		{
			m_running = false; 
			m_className = Subclass.classinfo.name;
		}
	}

	override string className() 
	{
		return m_className;
	}

	TestBase[] tests() 
	{
		return m_tests;
	}

	void register(TestBase firstChild, TestBase[] children...) 
	in
	{
		assert(firstChild !is null);
	}
	body {
		m_running = false;
		m_tests ~= firstChild;
		if((children !is null) && (children.length > 0))
			m_tests ~= children;
	}

	bool empty() {
		return m_tests.length == 0;
	}

	override bool isRunning() {
		return m_running;
	}

	override void run(TestResult result) {
		m_running = true;
		foreach(test; m_tests) {
			test.run(result);
		}
		m_running = false;
	}
}

////////////////////////////////////////////////////////////////////////////////

final class MainTestSuite : TestSuite!(MainTestSuite)
{
	private this()
	{
	}

	public static MainTestSuite create(TestBase[] children...)
	{
		auto s = new MainTestSuite;

		if(children !is null && children.length > 0)
			s.m_tests ~= children;
		return s;
	}

}

////////////////////////////////////////////////////////////////////////////////

MainTestSuite suite(TestBase[] children...)
{
	return MainTestSuite.create(children);
}
